
use std::collections::HashMap;
use std::str::FromStr;
use std::convert::TryFrom;

use std::sync::{Arc, RwLock};

use anyhow::{Result, anyhow};

use sqlx::{mysql::MySqlConnectOptions, ConnectOptions, MySqlPool, MySql, Pool};
use http_client::h1::H1Client as Client;
use http_client::{Config, HttpClient, Request};
use http_client::http_types;

use crate::node::{JsonRpcRequest, JsonRpcResponse};
use serde::{Serialize, Deserialize, de};

pub mod cache;

#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)]
pub struct StoreMinerSealer {
    pub node_name: String,
    pub node_host: String,
    pub node_type: String,
    pub node_api: String,
    pub node_token: String,
    pub node_data: Option<String>,
    pub miner_actor: i32,
}

#[derive(Clone)]
pub struct State {
    node: Arc<RwLock<HashMap<String, StoreMinerSealer>>>,
    client: Arc<Client>,
    db: Pool<MySql>,
}

impl State {
    // mysql://root:123456@192.168.0.8:3306/test120
    pub async fn new(db: &str) -> Result<Self> {
        let db = mysql_connect(db).await?;
        let config = Config::new().set_max_connections_per_host(10);
        let client = Client::try_from(config)?;
        let mut state = State{
            node: Arc::new(RwLock::new(HashMap::new())),
            client: Arc::new(client),
            db
        };
        let _ = state.load().await?;
        Ok(state)
    }

    pub fn miner_actor(&self, key: &str) -> Result<u64> {
        let node = self.node.read().unwrap();
        let info = node.get(key);
        match info {
            None => Err(anyhow!("sealer for {} not found.", key)),
            Some(sealer) => Ok(sealer.miner_actor as u64)
        }
    }

    pub fn client(&self) -> &Client {
        self.client.as_ref()
    }

    pub fn database(&self) -> MySqlPool {
        self.db.clone()
    }

    pub fn node(&self, key: &str) -> Option<StoreMinerSealer> {
        let lock = self.node.read().unwrap();
        let ret = lock.get(&key.to_owned());
        match ret {
            None => None,
            Some(o) => Some(o.clone())
        }
    }

    pub async fn load(&mut self) -> Result<HashMap<String, StoreMinerSealer>> {
        let query = "select * from miner_node";
        let sealers: Vec<StoreMinerSealer> = sqlx::query_as::<_, StoreMinerSealer>(query)
            .fetch_all(&self.db)
            .await?;
        if sealers.is_empty() {
            return Err(anyhow!("sealer not found from data."))
        }

        let mut node = self.node.write().unwrap();
        node.clear();

        for sealer in sealers {
            node.insert(sealer.node_name.clone(), sealer.clone());
        }
        Ok(node.clone())
    }

    pub fn show(&self) -> HashMap<String, StoreMinerSealer> {
        let node = self.node.read().unwrap();
        node.clone()
    }
}

pub async fn mysql_connect(url: &str) -> Result<Pool<MySql>> {
    log::info!("connect to database: {}", url);
    let mut options = MySqlConnectOptions::from_str(&url)?;
    options.disable_statement_logging();
    let pool = MySqlPool::connect_with(options).await?;
    Ok(pool)
}